package com.zws.cucumber.util;

import com.github.tomakehurst.wiremock.WireMockServer;
import com.github.tomakehurst.wiremock.client.ResponseDefinitionBuilder;
import com.github.tomakehurst.wiremock.client.WireMock;
import com.github.tomakehurst.wiremock.common.FileSource;
import com.github.tomakehurst.wiremock.common.Metadata;
import com.github.tomakehurst.wiremock.common.SingleRootFileSource;
import com.github.tomakehurst.wiremock.common.TextFile;
import com.github.tomakehurst.wiremock.matching.RequestPatternBuilder;
import com.github.tomakehurst.wiremock.stubbing.StubMapping;
import com.zws.cucumber.util.colorfullog.ZwsColorfulLogger;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.util.CollectionUtils;

import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;

import static com.github.tomakehurst.wiremock.common.Exceptions.throwUnchecked;
import static com.zws.cucumber.expander.ZwsStr.expand;
import static com.zws.cucumber.expr.ZwsExpr.evaluate;

/**
 * author: zws
 */
public class ZwsMockUtil {

    private static Logger logger = ZwsColorfulLogger.of(ZwsMockUtil.class);

    private static Map<String, String> cachedJsonStrings = new HashMap<>();
    private static Map<String, StubMapping> cachedMappings = new HashMap<>();

    private static volatile boolean haveMockedTimeout = false;

    private static int cachedWireMockPort = 0;

    private static String readTemplate(Resource resource) {
        return new TextFile(null) {
            @Override
            public InputStream getStream() {
                try {
                    return resource.getInputStream();
                } catch (IOException e) {
                    return throwUnchecked(e, InputStream.class);
                }
            }
            @Override
            public String name() {
                return "";
            }
        }.readContentsAsString();
    }

    private static int getWireMockPort(WireMockServer wireMockServer) {
        if (cachedWireMockPort == 0) {
            cachedWireMockPort = wireMockServer.port();
        }
        return cachedWireMockPort;
    }

    public static StubMapping loadMappingWithExpansionAndEvaluation(String filePath, Map<String, String> params) throws IOException {
        Map<String, Object> evaluatedParams = new HashMap<>();
        if (!CollectionUtils.isEmpty(params)) {
            params.forEach((k, v) -> {
                evaluatedParams.put(k, evaluate(k, expand(v)));
            });
        }
        StubMapping mapping = loadMapping(filePath, evaluatedParams);
        return mapping;
    }

    public static StubMapping loadMapping(String filePath, Map<String, Object> params) throws IOException {
        StubMapping mapping = cachedMappings.get(filePath);

        if (mapping == null) {
            String fixedFilePath = filePath;
            if (!filePath.contains("/") && !filePath.startsWith("classpath")) {
                fixedFilePath = "classpath*:/wiremock/**/" + filePath;
            }
            PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
            Resource[] resources = resolver.getResources(fixedFilePath);
            logger.debug("resources: {}", Arrays.toString(resources));

            String mappingJson = readTemplate(resources[0]);
            StubMapping origin = StubMapping.buildFrom(mappingJson);

            // read file just one time
            // is application/zip
            boolean isApplicationZip = false;
            if (origin.getResponse() != null
                    && origin.getResponse().getHeaders() != null
                    && origin.getResponse().getHeaders().getContentTypeHeader() != null) {
                isApplicationZip = origin.getResponse().getHeaders().getContentTypeHeader().hasValueMatching(WireMock.equalTo("application/zip"));
            }

            if (StringUtils.isNotBlank(origin.getResponse().getBodyFileName())) {
                FileSource fileSource = new SingleRootFileSource("src/test/resources");
                FileSource child = fileSource.child("__files");
                ResponseDefinitionBuilder rdb =  ResponseDefinitionBuilder.like(origin.getResponse())
                        .withBodyFile(null);

                if (isApplicationZip) {
                    rdb.withBody(child.getBinaryFileNamed(origin.getResponse().getBodyFileName()).readContents());
                } else {
                    rdb.withBody(child.getTextFileNamed(origin.getResponse().getBodyFileName()).readContentsAsString());
                }

                origin.setResponse(rdb.build());
            }

            cachedJsonStrings.put(filePath, mappingJson);
            cachedMappings.put(filePath, origin);

            mapping = copyMapping(origin);
        } else {
            mapping = copyMapping(mapping);
        }
        if (!CollectionUtils.isEmpty(params)) {
            if (mapping.getResponse().getTransformerParameters() == null) {
                ResponseDefinitionBuilder builder = ResponseDefinitionBuilder.like(mapping.getResponse());
                for (Entry<String, Object> entry : params.entrySet()) {
                    builder.withTransformerParameter(entry.getKey(), entry.getValue());
                }
                mapping.setResponse(builder.build());
            } else {
                mapping.getResponse().getTransformerParameters().putAll(params);
            }
        }
        return mapping;
    }

    public static StubMapping copyMapping(StubMapping origin) {
        StubMapping clone = new StubMapping();
        clone.setName(origin.getName());
        clone.setRequest(RequestPatternBuilder.like(origin.getRequest()).build());
        clone.setResponse(ResponseDefinitionBuilder.like(origin.getResponse()).build());
        clone.setMetadata(origin.getMetadata() != null ? new Metadata(origin.getMetadata()) : null);
        clone.setPriority(origin.getPriority());
        clone.setDirty(origin.isDirty());
        clone.setInsertionIndex(origin.getInsertionIndex());
        clone.setPostServeActions(origin.getPostServeActions());
        clone.setPersistent(origin.isPersistent());

        clone.setScenarioName(origin.getScenarioName());
        clone.setRequiredScenarioState(origin.getRequiredScenarioState());
        clone.setNewScenarioState(origin.getNewScenarioState());
        return clone;
    }

    public static boolean haveMockedTimeout() {
        return haveMockedTimeout;
    }

    public static void markMockedTimeout() {
        haveMockedTimeout = true;
    }

    public static void resetMockedTimeout() {
        haveMockedTimeout = false;
    }

    public static void setMockedTimeout(boolean haveMockedTimeout) {
        ZwsMockUtil.haveMockedTimeout = haveMockedTimeout;
    }

    public static void setMockedTimeout(StubMapping mapping) {
        if (mapping.getResponse().getFixedDelayMilliseconds() != null
                && mapping.getResponse().getFixedDelayMilliseconds() > 0) {
            setMockedTimeout(true);
        }
    }
}
